/*
 * 文件名称: LoginFilterBean.java Copyright 2011-2015 Ximalaya All right reserved.
 */
package top.zxybird.common.web.filter;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.StringUtils;

import top.zxybird.common.model.ClientInfo;
import top.zxybird.common.model.SsoSession;
import top.zxybird.common.util.ClientUtils;
import top.zxybird.common.util.CookieUtils;

/**
 * 两种path的串行检测chain如下:
 * <p>
 * 1. excludePathPatterns 不需要checkLogin的地址,包含在内则立即完成该项filter
 * <p>
 * 2. includePathPatterns 需要checkLogin的地址,包含在内并且check通过,则立即完成该项filter,若不通过则会返回ret=50
 * <p>
 * 
 * @author Ted Wang
 */
public class LoginFilterBean implements Filter, InitializingBean {
  private final static Logger LOG = LoggerFactory.getLogger(LoginFilterBean.class);
  public static final String USER_ID = "currentUserId";
  public static final String USER_SESSION_ID = "XIMALAYA_LOGIN_USER_SESSION_ID";
  public final static String REMEMBER_ME = "remember_me";
  private final static String needLoginResult = "{\"ret\":50,\"msg\":\"50\"}";
  private String environmentId;
  private List<String> includePathPatterns;
  private List<String> excludePathPatterns;
  private List<Pattern> preparedIncludePathPatterns = new ArrayList<Pattern>();
  private List<Pattern> preparedExcludePathPatterns = new ArrayList<Pattern>();

  public void destroy() {
    preparedIncludePathPatterns.clear();
    preparedExcludePathPatterns.clear();
  }

  public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain)
      throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;
    String uri = request.getRequestURI();
    SsoSession user = null;
    if (isExcludePath(uri)) {
      filterChain.doFilter(request, response);
    } else if (isIncludePath(uri)) {
      // 只处理include的uri，其他的直接放过
      user = parseSession(request);
      boolean isLogin = checkLogin(request, user);
      if (isLogin) {
        injectUserIntoRequest(request, user);
        filterChain.doFilter(request, response);
      } else {
        LOG.warn("user {} login session is expired, need to relogin again.", user);
        resposeNeedLoginRet(response);
      }
    } else {
      // 其他情况只解析cookie
      user = parseSession(request);
      injectUserIntoRequest(request, user);
      filterChain.doFilter(request, response);
    }
  }

  /**
   */
  public void injectUserIntoRequest(HttpServletRequest request, SsoSession session) {
    if (session.getUid() != null) {
      request.setAttribute(USER_ID, session.getUid().toString());
    }
    request.setAttribute(USER_SESSION_ID, session.getTicket());
    request.setAttribute(REMEMBER_ME, session.getIsRemember());
  }

  /**
   * 获取当前登录用户UID
   */
  public static Long getCurrentLongUid(ServletRequest request) {
    String id = getCurrentUid(request);
    try {
      return id == null ? null : Long.parseLong(id);
    } catch (Exception e) {
      LOG.error("解析UID:" + e.getMessage());
      return null;
    }
  }

  public static String getCurrentSessionId(ServletRequest request) {
    return (String) request.getAttribute(USER_SESSION_ID);
  }

  public static String getCurrentUid(ServletRequest request) {
    return (String) request.getAttribute(USER_ID);
  }

  private SsoSession parseSession(HttpServletRequest request) {
    SsoSession result = getSsoSession(request);
    // 如果cookies中没有值,则检查url是否带有_token参数
    if (null == result) {
      // 如果cookies获取不到登录信息,默认不从queryString上获取uid
      Long uid = null;
      String token = request.getParameter("token");
      result = new SsoSession(null, token, true);
    } else {
      result.setIsRemember(true);
    }
    return result;
  }

  private SsoSession getSsoSession(HttpServletRequest request) {
    SsoSession result = null;
    String loginToken = CookieUtils.getLoginToken(environmentId, request.getCookies());
    if (!StringUtils.isEmpty(loginToken)) {
      boolean isRemeberMe = CookieUtils.isRemeberMe(environmentId, request.getCookies());
      result = new SsoSession(loginToken, isRemeberMe);
    }
    return result;
  }

  private boolean checkLogin(HttpServletRequest request, SsoSession user) {
    boolean isLogin = false;
    if (user.notEmpty()) {
      ClientInfo client = ClientUtils.assembleClientInfo(request);
      try {
        isLogin = false;
        // isLogin = sessionService.checkSession(user, client);
      } catch (Exception e) {
        LOG.error("check login status failed, set default isLogin true.", e);
        isLogin = true;
      }
    }
    return isLogin;
  }

  private boolean isIncludePath(String path) {
    for (Pattern includePattern : preparedIncludePathPatterns) {
      if (includePattern.matcher(path).find()) {
        return true;
      }
    }
    return false;
  }

  private boolean isExcludePath(String path) {
    for (Pattern excludePattern : preparedExcludePathPatterns) {
      if (excludePattern.matcher(path).find()) {
        return true;
      }
    }
    return false;
  }

  /**
   * 返回http status 401，body中返回ret=50,msg=50
   */
  private void resposeNeedLoginRet(HttpServletResponse response) throws IOException {
    response.setContentType("text/html");
    response.setCharacterEncoding("UTF-8");
    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    PrintWriter writer = response.getWriter();
    writer.write(needLoginResult);
    writer.flush();
  }

  public void init(FilterConfig arg0) throws ServletException {

  }

  public void afterPropertiesSet() throws Exception {
    preparedIncludePathPatterns.clear();
    preparedExcludePathPatterns.clear();
    if (null != getIncludePathPatterns()) {
      for (String includePattern : getIncludePathPatterns()) {
        preparedIncludePathPatterns.add(Pattern.compile(includePattern, Pattern.CASE_INSENSITIVE));
      }
    }
    if (null != getExcludePathPatterns()) {
      for (String excludePattern : getExcludePathPatterns()) {
        preparedExcludePathPatterns.add(Pattern.compile(excludePattern, Pattern.CASE_INSENSITIVE));
      }
    }
  }

  public String getEnvironmentId() {
    return environmentId;
  }

  public void setEnvironmentId(String environmentId) {
    this.environmentId = environmentId;
  }

  public List<String> getExcludePathPatterns() {
    return excludePathPatterns;
  }

  public void setExcludePathPatterns(List<String> excludePathPatterns) {
    this.excludePathPatterns = excludePathPatterns;
  }

  public List<String> getIncludePathPatterns() {
    return includePathPatterns;
  }

  public void setIncludePathPatterns(List<String> includePathPatterns) {
    this.includePathPatterns = includePathPatterns;
  }

}
